package com.unitd.modules.configcenter.controller;

import com.github.pagehelper.PageInfo;
import com.unitd.frame.comm.utils.BeanUtils;
import com.unitd.frame.comm.utils.StringUtils;
import com.unitd.frame.springweb.controller.result.AjaxResult;
import com.unitd.frame.springweb.controller.result.PageResult;
import com.unitd.modules.comm.exception.ConfigServerException;
import com.unitd.modules.comm.web.BaseController;
import com.unitd.modules.configcenter.component.ConfigStateHolder;
import com.unitd.modules.configcenter.component.CryptComponent;
import com.unitd.modules.configcenter.dto.EncryptDTO;
import com.unitd.modules.configcenter.entity.AppConfigs;
import com.unitd.modules.configcenter.entity.AppSecret;
import com.unitd.modules.configcenter.entity.Apps;
import com.unitd.modules.configcenter.entity.OperateLogs;
import com.unitd.modules.configcenter.service.IAppConfigsService;
import com.unitd.modules.configcenter.service.IAppsService;
import com.unitd.modules.configcenter.service.IOperateLogsService;
import com.unitd.modules.configcenter.util.ConfigParseUtils;
import com.unitd.modules.sso.util.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;

import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Hudan
 * @version 1.0
 * @desc 配置中心应用信息管理前端控制器
 * @filename AppController.java
 * @copyright www.unitd.com
 * @date 2018/1/31
 */
@RestController
@RequestMapping("/admin/config")
public class ConfigController extends BaseController {

	private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

	@Autowired
	private IAppConfigsService appConfigsService;
	@Autowired
	private IAppsService appsService;
	@Autowired
	private IOperateLogsService operateLogsService;
	@Autowired
	private CryptComponent cryptComponent;
	@Autowired
	private ConfigStateHolder configStateHolder;

	/**
	 * @desc 获取信息列表json数据, 方便列表页通过ajax获取并展示
	 * @param param 配置信息
	 * @return json数据
	 */
	@RequestMapping(value = "list", method = RequestMethod.POST)
	public PageResult list(@RequestBody AppConfigs param) {
		BeanUtils.setClassPropDefValue(param);

		// 获取分页数据信息
		Map<String, Integer> pageMap = getPageMap();

		// 查询列表数据
		PageInfo<AppConfigs> pageData = appConfigsService.getPageData(pageMap.get("pageNo"), pageMap.get("pageSize"), param);
		for (AppConfigs appconfigEntity : pageData.getList()) {
			String appName = "";
			if (org.apache.commons.lang3.StringUtils.isBlank(appconfigEntity.getAppIds())) {
				appName = "全局配置";
			} else {
				String[] appIds = appconfigEntity.getAppIds().split(",");
				for (int i = 0; i < appIds.length; i++) {
					Apps appEntity = appsService.selectByPrimaryKey(appIds[i]);
					appName = appName + appEntity.getAlias() + (i < appIds.length - 1 ? "," : "");
				}
			}
			appconfigEntity.setAppName(appName);
		}

		return new PageResult(pageData.getTotal(), pageData.getList());
	}

	/**
	 * @desc 根据主键ID, 获取对应的详情信息
	 * @param id 主键ID
	 * @return 自定义的通用json对象
	 */
	@RequestMapping(value = "{id}", method = RequestMethod.GET)
	public AjaxResult getData(@PathVariable("id") String id) {
		// 根据主键获取属性配置所属环境信息
		AppConfigs entity = appConfigsService.selectByPrimaryKey(id);
		if(!BeanUtils.isNull(entity) && StringUtils.isNull(entity.getAppIds())) {
			entity.setGlobal(true);
		}

		// 判断是否必须需要配置信息的profile信息
		SecurityUtil.requireProfileGanted(entity.getEnv());

		return new AjaxResult(entity);
	}

	/**
	 * @desc 新增/修改数据
	 * @param param 前端需要进行新增/修改的数据
	 * @return 自定义的通用json对象
	 */
	@RequestMapping(value = "edit", method = RequestMethod.POST)
	public AjaxResult edit(@RequestBody AppConfigs param) {

		// 判断是否必须需要配置信息的profile信息
		SecurityUtil.requireProfileGanted(param.getEnv());

		// 新增数据
		if (StringUtils.isNull(param.getId())) {
			param.setId(null);
			if(StringUtils.isNull(param.getVersion())) {
				param.setVersion("release");
			}
			if (!param.getGlobal() && StringUtils.isBlank(param.getAppIds())) {
				throw new ConfigServerException(4001, "非全局绑定应用不能为空");
			}

			if (StringUtils.isBlank(param.getEnv())) {
				throw new ConfigServerException(4001, "绑定环境profile不能为空");
			}

			if (param.getType().intValue() == 2 && StringUtils.isBlank(param.getName())) {
				throw new ConfigServerException(4001, "配置项名称不能空");
			}

			appConfigsService.insertSelective(param);
			// 保存日志
			operateLogsService.insertSelective(SecurityUtil.getOperateLog().addBizData("id", param.getId()));
		} else {
			// 修改数据
			if (StringUtils.isZero(param.getId())) {
				throw new ConfigServerException(1003, "id参数缺失");
			}

			AppConfigs data = appConfigsService.selectByPrimaryKey(param.getId());

			OperateLogs operateLog = SecurityUtil.getOperateLog();
			operateLog.setBeforeData(data.getContents());
			operateLog.setAfterData(param.getContents());

			if (!param.getGlobal() && StringUtils.isBlank(param.getAppIds())) {
				throw new ConfigServerException(4001, "非全局绑定应用不能为空");
			}

			operateLog.addBizData("beforeAppIds", data.getAppIds()).addBizData("afterAppIds", param.getAppIds());
			data.setAppIds(param.getAppIds());

			operateLog.addBizData("beforeVersion", data.getVersion()).addBizData("afterVersion", param.getVersion());
			data.setVersion(param.getVersion());

			String orignContents = data.getContents();
			data.setContents(param.getContents());
			appConfigsService.updateByPrimaryKeySelective(data);

			// 属性配置信息更改发布后, 需要向zookeeper和配置中心客户端发送通知,告知配置属性信息已更改
			publishConfigChangeEvent(orignContents, data);

			// 保存日志
			operateLogsService.insertSelective(operateLog);
		}

		return new AjaxResult(true, "数据保存成功!");
	}

	/**
	 * @desc 根据主键ID, 删除响应的数据
	 * @param id 主键ID
	 * @return 自定义的通用json对象
	 */
	@RequestMapping(value = "delete/{id}", method = RequestMethod.GET)
	public AjaxResult delete(@PathVariable("id") String id) {
		SecurityUtil.requireSuperAdmin();

		AppConfigs entity = appConfigsService.selectByPrimaryKey(id);
		if (entity != null) SecurityUtil.requireProfileGanted(entity.getEnv());

		// 全局配置
		if (entity.getGlobal()) SecurityUtil.requireSuperAdmin();
		int delete = entity == null ? 0 : appConfigsService.deleteByPrimaryKey(id);
		operateLogsService.insertSelective(SecurityUtil.getOperateLog().addBizData("id", entity.getId()));

		if (delete > 0) {
			return new AjaxResult(true, "删除数据成功!");
		} else {
			return new AjaxResult(false, "删除数据失败!");
		}
	}

	/**
	 * @desc 上传属性配置文件
	 * @param file 属性配置文件
	 * @return 自定义的通用json对象
	 */
	@RequestMapping(value = "upload", method = RequestMethod.POST)
	public AjaxResult uploadConfigFile(@RequestParam("file") MultipartFile file) {
		try {
			Map<String, String> result = new HashMap<>(2);
			result.put("fileName", file.getOriginalFilename());
			result.put("content", new String(file.getBytes(), StandardCharsets.UTF_8));

			return new AjaxResult(true, "属性配置文件上传成功!", result);
		} catch (Exception e) {
			throw new ConfigServerException(9999, "上传失败");
		}
	}

	/**
	 * @desc 拷贝配置属性信息
	 * @param params 需要拷贝的属性信息
	 * @return 自定义的通用json对象
	 */
	@RequestMapping(value = "copy", method = RequestMethod.POST)
	public AjaxResult copyConfig(@RequestBody Map<String, String> params) {
		String from = params.get("from");
		SecurityUtil.requireProfileGanted(from);
		String to = params.get("to");
		SecurityUtil.requireProfileGanted(to);

		Example example = new Example(AppConfigs.class);
		example.createCriteria().andEqualTo("env", from);
		List<AppConfigs> configList = appConfigsService.selectByExample(example);
		if (configList == null || configList.isEmpty()) {
			throw new ConfigServerException(1001, "Profile[" + from + "]无任何配置");
		}

		for (AppConfigs entity : configList) {
			entity.setId(null);
			entity.setEnv(to);
			appConfigsService.insertSelective(entity);
		}

		return new AjaxResult(true, "拷贝属性配置信息成功!");
	}

	/**
	 * @desc 加密属性配置信息
	 * @param param 加密工具信息
	 * @return 自定义的通用json对象
	 */
	@RequestMapping(value = "encrypt", method = RequestMethod.POST)
	public AjaxResult encryptConfig(@RequestBody EncryptDTO param) {
		if (StringUtils.isAnyBlank(param.getEnv(), param.getAppId(), param.getData(), param.getEncryptType())) {
			throw new ConfigServerException(1001, "请完整填写输入项");
		}
		SecurityUtil.requireProfileGanted(param.getEnv());
		Apps entity = appsService.selectByPrimaryKey(param.getAppId());

		AppSecret appSecret = cryptComponent.getAppSecret(entity.getId(), param.getEnv(), param.getEncryptType());

		if (appSecret == null) throw new ConfigServerException(1001, "无[" + param.getEncryptType() + "]密钥配置");
		String encodeStr = cryptComponent.encode(appSecret, param.getData().trim());

		return new AjaxResult(true, "加密属性配置信息成功!", encodeStr);
	}

	/**
	 * @desc 属性配置信息更改发布后, 需要向zookeeper和配置中心客户端发送通知,告知配置属性信息已更改
	 * @param orignContents 原始属性配置信息
	 * @param entity 属性配置对象
	 */
	private void publishConfigChangeEvent(String orignContents, AppConfigs entity) {
		try {
			logger.info("begin publishConfigChangeEvent,{}-{}", entity.getAppIds(), entity.getEnv());
			//更新后的配置
			Map<String, Object> currentConfigMap = ConfigParseUtils.parseConfigToKVMap(entity);

			entity.setContents(orignContents);
			Map<String, Object> orignConfigMap = ConfigParseUtils.parseConfigToKVMap(entity);

			List<ConfigStateHolder.ConfigState> configStates;
			if (entity.getGlobal()) {
				configStates = configStateHolder.get(entity.getEnv());
			} else {
				configStates = new ArrayList<>();
				String[] appIds = entity.getAppIds().split(",");
				for (int i = 0; i < appIds.length; i++) {
					Apps appEntity = appsService.selectByPrimaryKey(appIds[i]);
					List<ConfigStateHolder.ConfigState> tmpList = configStateHolder.get(appEntity.getName(), entity.getEnv());
					if (tmpList != null && !tmpList.isEmpty()) configStates.addAll(tmpList);
				}
			}

			if (configStates.isEmpty()) return;

			// zookeeper 通知, 同一环境+app只需要通知一次，所以这里保存一个通知状态
			Set<String> publishList = new HashSet<>();
			for (ConfigStateHolder.ConfigState configState : configStates) {
				Set<String> keys = currentConfigMap.keySet();

				Map<String, String> changedMap = new HashMap<>();
				for (String key : keys) {
					Object orignValue = orignConfigMap.get(key);
					Object currentVaule = currentConfigMap.get(key);
					if (!Objects.equals(orignValue, currentVaule)) {
						changedMap.put(key, currentVaule.toString());
					}
				}
				String publishedKey = configState.getEnv() + ":" + configState.getAppName();
				if (ConfigStateHolder.SYNC_TYPE_ZK.equals(configState.getSyncType()) == false
						|| publishList.contains(publishedKey) == false) {
					configState.publishChangeConfig(changedMap);
					publishList.add(publishedKey);
					logger.info("publishConfigChangeEvent,env:{},appName:{},changeConfig:{}", configState.getEnv(), configState.getAppName(), changedMap);
				}
			}
		} catch (Exception e) {
			logger.error("publishConfigChangeEvent error", e);
		}
	}
}